#include "GLWidget.h"
#include "../Core/Exceptions.h"
#include "../Core/Materials.h"
#include "../Core/Constants.h"

using namespace std;

namespace cagd
{
    // default/special constructor
    GLWidget::GLWidget(QWidget *parent, const QGLFormat &format): QGLWidget(format, parent)
    {
        _timer = new QTimer(this);
        _timer->setInterval(0);

        connect(_timer, SIGNAL(timeout()), this, SLOT(animate()));
    }

    // destructor
    GLWidget::~GLWidget()
    {
        // free every dynamically allocated object
    }

    // initializer method
    // it is called once before the first call to paintGL() or resizeGL(),
    // and then once whenever the widget has been assigned a new QGLContext
    void GLWidget::initializeGL()
    {
        glewInit();

        // define a projection matrix
        glMatrixMode(GL_PROJECTION);

        _aspect = (GLfloat)width() / (GLfloat)height();
        _fovy = 45.0;
        _z_near = 1.0;
        _z_far = 1000.0;

        glLoadIdentity();
        gluPerspective(_fovy, _aspect, _z_near, _z_far);

        // define a model view matrix
        glMatrixMode(GL_MODELVIEW);

        _eye[2] = 6.0;
        _up[1] = 1.0;

        glLoadIdentity();
        gluLookAt(_eye[0], _eye[1], _eye[2], _center[0], _center[1], _center[2], _up[0], _up[1], _up[2]);

        // enable the required OpenGL states

        // enable depth testing
        glEnable(GL_DEPTH_TEST);

        // ensure unit normal vectors
        glEnable(GL_NORMALIZE);

        // enable default lighting
        glEnable(GL_LIGHTING);

        // enable default light source 0, which is a directional light with grey ambient, diffuse and specular intensities
        glEnable(GL_LIGHT0);

        // set the background color
        glClearColor(0.0, 0.0, 0.0, 1.0);

        //  create/load your geometry

        if (!_mouse.LoadFromOFF("Models/mouse.off", GL_TRUE))
        {
            throw Exception("Could not load the model file!");
        }
        else
        {
            if (!_mouse.UpdateVertexBufferObjects())
            {
                throw Exception("Could not create the vertex buffer object of the model!");
            }
        }

        _angle = 0.0;
        _timer->start();

        _trans_x = _trans_y = _trans_z = 0.0;
        _scale = 2.0;
        _angle_x = _angle_y = _angle_z = 0.0;

        _random_material = 1;
    }

    // rendering method
    void GLWidget::paintGL()
    {
        // clear the color and depth buffers
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

        // save (or duplicate) the modelview matrix
        glPushMatrix();

        // perform your transformations and render your geometry
        glRotated(_angle_x, 1.0, 0.0, 0.0);
        glRotated(_angle_y, 0.0, 1.0, 0.0);
        glRotated(_angle_z, 0.0, 0.0, 1.0);

        glTranslated(_trans_x, _trans_y, _trans_z);

        glScaled(_scale, _scale, _scale);

        switch (_random_material) {
        case 1:
            MatFBBrass.Apply();
            break;
        case 2:
            MatFBEmerald.Apply();
            break;
        case 3:
            MatFBGold.Apply();
            break;
        case 4:
            MatFBPearl.Apply();
            break;
        case 5:
            MatFBRuby.Apply();
            break;
        case 6:
            MatFBSilver.Apply();
            break;
        case 7:
            MatFBTurquoise.Apply();
            break;
        }

        _mouse.Render();

        // restore the original modelview matrix
        glPopMatrix();
    }

    // resizing method
    void GLWidget::resizeGL(int w, int h)
    {
        // set the viewport
        glViewport(0, 0, w, h);

        // define the projection matrix
        glMatrixMode(GL_PROJECTION);

        _aspect = (GLfloat)w / (GLfloat)h;

        glLoadIdentity();
        gluPerspective(_fovy, _aspect, _z_near, _z_far);

        // revert to the modelview matrix
        glMatrixMode(GL_MODELVIEW);

        // update your scene, i.e., invoke indirectly the method paintGL()
        updateGL();

    }

    // implementation of public event handlers
    void GLWidget::set_angle_x(int value)
    {
        if (_angle_x != (double)value)
        {
            _angle_x = value;
            updateGL();
        }
    }

    void GLWidget::set_angle_y(int value)
    {
        if (_angle_y != (double)value)
        {
            _angle_y = value;
            updateGL();
        }
    }

    void GLWidget::set_angle_z(int value)
    {
        if (_angle_z != (double)value)
        {
            _angle_z = value;
            updateGL();
        }
    }

    void GLWidget::set_trans_x(double value)
    {
        if (_trans_x != value)
        {
            _trans_x = value;
            updateGL();
        }
    }

    void GLWidget::set_trans_y(double value)
    {
        if (_trans_y != value)
        {
            _trans_y = value;
            updateGL();
        }
    }

    void GLWidget::set_trans_z(double value)
    {
        if (_trans_z != value)
        {
            _trans_z = value;
            updateGL();
        }
    }

    void GLWidget::set_zoom_factor(double value)
    {
        if (_scale != value)
        {
            _scale = value;
            updateGL();
        }
    }

    void GLWidget::reset_to_original_view()
    {
        _trans_x = _trans_y = _trans_z = 0.0;
        _scale = 1.0;
        _angle_x = _angle_y = _angle_z = 0.0;

        updateGL();
    }

    void GLWidget::load_mouse() {
        randomizeMat();

        if (!_mouse.LoadFromOFF("Models/mouse.off", GL_TRUE))
        {
            throw Exception("Could not load the model file!");
        }
        else
        {
            if (!_mouse.UpdateVertexBufferObjects())
            {
                throw Exception("Could not create the vertex buffer object of the model!");
            }
        }

        updateGL();
    }

    void GLWidget::load_elephant() {
        randomizeMat();

        if (!_mouse.LoadFromOFF("Models/elephant.off", GL_TRUE))
        {
            throw Exception("Could not load the model file!");
        }
        else
        {
            if (!_mouse.UpdateVertexBufferObjects())
            {
                throw Exception("Could not create the vertex buffer object of the model!");
            }
        }

        updateGL();
    }

    void GLWidget::randomizeMat() {
        _random_material = 1 + (rand() % 7);

        cout << _random_material << endl;
    }

    void GLWidget::animate()
    {
        GLfloat *vertex = _mouse.MapVertexBuffer(GL_READ_WRITE);
        GLfloat *normal = _mouse.MapNormalBuffer(GL_READ_ONLY);

        _angle += DEG_TO_RADIAN;
        if (_angle >= TWO_PI)
            _angle -= TWO_PI;

        GLfloat scale = sin(_angle) / 3000.0;

        for (GLuint i = 0; i < _mouse.VertexCount(); ++i)
        {
            for (GLuint coordinate = 0; coordinate < 3; ++ coordinate, ++vertex, ++normal)
                *vertex += scale * (*normal);
        }

        _mouse.UnmapVertexBuffer();
        _mouse.UnmapNormalBuffer();

        updateGL();
    }
}

